package com.zhtools.wx.impl;

import com.zhtools.utils.HttpClientUtils;
import com.zhtools.wx.WXConfig;
import com.zhtools.wx.WXPayRequestParams;
import com.zhtools.wx.WXPayService;
import com.zhtools.wx.bean.*;
import com.zhtools.wx.exception.WXPayException;
import com.zhtools.wx.utils.WXPayUtil;
import com.zhtools.wx.utils.WXUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zhanghao
 */
public abstract class AbstractWXPayService implements WXPayService {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractWXPayService.class);

    private static final String PAY_API_REQUEST = ">>> 微信支付统一下单接口请求报文:{}";
    private static final String PAY_API_RESPONSE = ">>> 微信支付统一下单接口请求响应:{}";
    private static final String PAY_CALLBACK_BODY = ">>> 微信支付回调请求体内容:{}";

    private static final String EMPTY_STRING = "";
    private static final String REQUEST_WX_SERVER_EXCEPTION = "请求微信服务器接口异常";

    //获取沙箱环境密钥接口API地址
    private static final String SENDBOX_API = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey";

    //域名
    private static final String API_DOMAIN = "https://api.mch.weixin.qq.com";
    //微信公众号支付统一下单接口地址
    private static final String PAY_API = "/pay/unifiedorder";
    //微信公众号支付查询订单详情接口地址
    private static final String ORDER_QUERY_API = "/pay/orderquery";
    //微信公众号退款申请接口地址
    private static final String REFUND_API = "/secapi/pay/refund";
    //微信公众号退款明细查询接口地址
    private static final String REFUND_QUERY_API = "/pay/refundquery";
    //微信下载对账单接口地址
    private static final String DOWNLOAD_BILL_API = "/pay/downloadbill";

    //沙箱环境路径
    protected String sendBoxProfilePath = EMPTY_STRING;

    /**
     * 获取微信配置信息
     */
    protected abstract WXConfig getWxConfig();

    private String getSignKey() {
        String signKey = EMPTY_STRING;
        if (StringUtils.isEmpty(this.sendBoxProfilePath)) {
            signKey = getWxConfig().getSignKey();
            LOGGER.info("获取正式环境密钥:{}", signKey);
        } else {
            signKey = getWxConfig().getSendBoxSecret();
            LOGGER.info("获取沙箱环境密钥:{}", signKey);
        }
        return signKey;
    }

    private String getNotifyUrl() {
        String notifyUrl = EMPTY_STRING;
        if (StringUtils.isEmpty(this.sendBoxProfilePath)) {
            notifyUrl = getWxConfig().getNotifyUrl();
            LOGGER.info("获取正式环境支付成功回调地址:{}", notifyUrl);
        } else {
            notifyUrl = getWxConfig().getSendBoxNotifyUrl();
            LOGGER.info("获取沙箱环境支付成功回调地址:{}", notifyUrl);
        }
        return notifyUrl;
    }

    private String getApiAddress(String apiPath) {
        return API_DOMAIN + this.sendBoxProfilePath + apiPath;
    }

    private String request(String data) throws WXPayException {
        String url = getApiAddress(PAY_API);
        String result = EMPTY_STRING;
        Map<String, String> header = new HashMap<>();
        header.put("Content-Type", "text/xml");
        header.put("User-Agent", "wxpay sdk java v1.0 " + getWxConfig().getMchId());
        try {
            LOGGER.info("统一下单请求地址:{}", url);
            result = HttpClientUtils.doPost(url, data, header);
        } catch (Exception e) {
            throw new WXPayException(REQUEST_WX_SERVER_EXCEPTION, e);
        }
        return result;
    }

    @Override
    public GetSandBoxSecretResponse getSandBoxSecret() throws WXPayException {
        String key = getWxConfig().getSignKey();
        String mch_id = getWxConfig().getMchId();
        String nonce_str = WXUtil.generateNonceStr();

        Map<String, String> params = new HashMap<>();
        params.put("mch_id", mch_id);
        params.put("nonce_str", nonce_str);

        LOGGER.info("执行获取沙箱环境密钥接口!签名KEY:{}", key);
        LOGGER.info("执行获取沙箱环境密钥接口!请求地址:{}", SENDBOX_API);
        String requestBody = EMPTY_STRING;
        try {
            requestBody = WXPayUtil.generateSignedXml(params, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.info("执行获取沙箱环境密钥接口!请求体:{}", requestBody);

        String result = EMPTY_STRING;
        try {
            result = HttpClientUtils.doPost(SENDBOX_API, requestBody, null);
        } catch (Exception e) {
            throw new WXPayException("请求获取沙箱环境密钥异常!", e);
        }
        LOGGER.info("请求获取沙箱环境密钥接口响应:{}", result);
        if (StringUtils.isEmpty(result)) {
            throw new WXPayException("请求获取沙箱环境密钥异常!原因:接口响应结果为空!");
        }
        return GetSandBoxSecretResponse.build(result);
    }

    @Override
    public UnityOrderResponse unityOrder(WXPayOrder payOrder) throws WXPayException {
        String signKey = getSignKey();
        String notifyUrl = getNotifyUrl();
        String xml = payOrder.toXml(getWxConfig().getAppId(), getWxConfig().getMchId(), notifyUrl, signKey);
        LOGGER.info(PAY_API_REQUEST, xml);
        String responseStr = request(xml);
        LOGGER.info(PAY_API_RESPONSE, responseStr);
        return UnityOrderResponse.formXmlResponse(responseStr, signKey);
    }

    @Override
    public OrderQueryResponse orderQuery(String outTradeNo) throws WXPayException {
        String url = getApiAddress(ORDER_QUERY_API);
        String key = getSignKey();
        LOGGER.info("执行获取订单详情接口!签名KEY:{}", key);
        LOGGER.info("执行获取订单详情接口!请求地址:{}", url);

        String requestXml = EMPTY_STRING;
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put("appid", getWxConfig().getAppId());
        requestParams.put("mch_id", getWxConfig().getMchId());
        requestParams.put("out_trade_no", outTradeNo);
        requestParams.put("nonce_str", WXUtil.generateNonceStr());
        try {
            requestXml = WXPayUtil.generateSignedXml(requestParams, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.info("执行获取订单详情接口!请求体:{}", requestXml);
        String result = EMPTY_STRING;
        try {
            result = HttpClientUtils.doPost(url, requestXml, null);
        } catch (Exception e) {
            throw new WXPayException("请求获取订单详情异常!", e);
        }
        LOGGER.info("请求获取订单详情接口响应:{}", result);
        if (StringUtils.isEmpty(result)) {
            throw new WXPayException("请求获取订单详情异常!原因:接口响应结果为空!");
        }
        return OrderQueryResponse.build(result, key);
    }

    @Override
    public FefundResponse refund(WXPayRequestParams params) throws WXPayException {
        String signKey = getSignKey();
        String url = getApiAddress(REFUND_API);
        String pwd = getWxConfig().getCerPassword();
        String filePath = getWxConfig().getCerFilePath();

        LOGGER.info("执行微信退款申请接口!签名KEY:{}", signKey);
        LOGGER.info("执行微信退款申请接口!请求地址:{}", url);
        LOGGER.info("执行微信退款申请接口!证书密码:{}", pwd);
        LOGGER.info("执行微信退款申请接口!证书文件路径:{}", filePath);
        String xml = params.toXml(getWxConfig().getAppId(), getWxConfig().getMchId(), signKey);
        LOGGER.info("执行微信退款申请接口请求体:{}", xml);
        String responseStr = EMPTY_STRING;
        try {
            responseStr = HttpClientUtils.doPostWithSSL(url, xml, null, pwd, filePath);
        } catch (Exception e) {
            throw new WXPayException("请求微信退款申请接口异常!", e);
        }
        LOGGER.info("执行微信退款申请接口响应:{}", responseStr);
        return FefundResponse.build(responseStr, signKey);
    }

    @Override
    public RefundQueryResponse refundQuery(String outTradeNo) throws WXPayException {
        String url = getApiAddress(REFUND_QUERY_API);
        String key = getSignKey();
        LOGGER.info("执行退款明细查询接口!签名KEY:{}", key);
        LOGGER.info("执行退款明细查询接口!请求地址:{}", url);

        String requestXml = EMPTY_STRING;
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put("appid", getWxConfig().getAppId());
        requestParams.put("mch_id", getWxConfig().getMchId());
        requestParams.put("out_trade_no", outTradeNo);
        requestParams.put("nonce_str", WXUtil.generateNonceStr());
        try {
            requestXml = WXPayUtil.generateSignedXml(requestParams, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.info("执行退款明细查询接口!请求体:{}", requestXml);
        String result = EMPTY_STRING;
        try {
            result = HttpClientUtils.doPost(url, requestXml, null);
        } catch (Exception e) {
            throw new WXPayException("请求退款明细查询接口异常!", e);
        }
        LOGGER.info("执行退款明细查询接口响应:{}", result);
        if (StringUtils.isEmpty(result)) {
            throw new WXPayException("请求退款明细查询接口异常!原因:接口响应结果为空!");
        }
        return RefundQueryResponse.build(result, key);
    }

    @Override
    public String downLoadBill(DownLoadBillRquest params) throws WXPayException {
        String signKey = getSignKey();
        String url = getApiAddress(DOWNLOAD_BILL_API);

        LOGGER.info("执行微信下载对账单接口!签名KEY:{}", signKey);
        LOGGER.info("执行微信下载对账单接口!请求地址:{}", url);
        String xml = params.toXml(getWxConfig().getAppId(), getWxConfig().getMchId(), signKey);
        LOGGER.info("执行微信下载对账单接口请求体:{}", xml);
        String responseStr = EMPTY_STRING;
        try {
            responseStr = HttpClientUtils.doPost(url, xml, null);
        } catch (Exception e) {
            throw new WXPayException("请求微信下载对账单接口异常!", e);
        }
        LOGGER.info("执行微信下载对账单接口响应:{}", responseStr);
        return responseStr;
    }

    @Override
    public WXPayCallBackResponse payCallBack(String body) throws WXPayException {
        LOGGER.info(PAY_CALLBACK_BODY, StringUtils.isEmpty(body) == true ? "" : body);
        return WXPayCallBackResponse.formXmlResponse(body, getSignKey());
    }

    @Override
    public String responseSuccess() {
        String data = EMPTY_STRING;
        Map<String, String> result = new HashMap<>();
        result.put("return_code", "SUCCESS");
        result.put("return_msg", "OK");
        try {
            data = WXPayUtil.mapToXml(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }

    @Override
    public String responseFail(String message) {
        String data = EMPTY_STRING;
        Map<String, String> result = new HashMap<>();
        result.put("return_code", "FAIL");
        result.put("return_msg", message);
        try {
            data = WXPayUtil.mapToXml(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
}
